use crate::{PROJECT_DIR, arch::Arch, machine_config::MachineConfig};
use os_xtask_utils::{BinUtil, Cargo, CommandExt, Ext, Qemu, dir};
use std::{
    collections::{HashMap, HashSet},
    ffi::OsString,
    path::PathBuf,
    str::FromStr,
};

/// 子命令asm bin需要这些参数

#[derive(Clone, Args)]
pub(crate) struct BuildArgs {
    #[clap(
        short,
        long,
        help = "指定硬件平台，在config/machine_features.toml设定好的，比如qemu.virt-riscv64,\
    qemu是厂商，virt-riscv64就是machine。"
    )]
    pub machine: String,

    #[clap(long, help = "是否是debug模式编译")]
    pub debug: bool,
}

#[derive(Clone, Args)]
pub(crate) struct OutArgs {
    #[clap(flatten)]
    build: BuildArgs,

    // sbi编译还是benos编译
    #[clap(
        short,
        long,
        default_value = "benos",
        help = "benos:表示为benos编译，sbi:表示编译固件， 如果我们想把sbi和benos链接成\
        \n一个bin文件的话，在config/machine_features.toml中添加features = [\"with_benos\"]。"
    )]
    target: String,

    #[clap(short, long, help = "定制输出的路径和文件名")]
    output: Option<PathBuf>,
}

/// 需要OutArg参数的有asm和bin子命令
impl OutArgs {
    pub fn asm(self) {
        let Self {
            build,
            target,
            output,
            ..
        } = self;
        let build = BuildConfig::from_args(build);
        build.invoke(&target, || {
            let mut cargo = Cargo::new_naked();
            cargo.args(["-C", &target]);
            cargo.arg("build");
            cargo
        });

        let obj = build.target_file_path(&target);
        let out = output.unwrap_or_else(|| PROJECT_DIR.join(format!("target/{target}.asm")));
        println!("Asm file dumps to {} .", out.display());
        dir::create_parent(&out).unwrap();
        std::fs::write(out, BinUtil::objdump().arg(obj).arg("-d").output().stdout).unwrap();
    }

    #[inline]
    pub fn bin(self) -> PathBuf {
        let Self {
            build,
            target,
            output,
            ..
        } = self;
        BuildConfig::from_args(build).bin(&target, output)
    }
}

#[derive(Debug, Args)]
pub(crate) struct GdbArgs {
    #[clap(short, long, help = "指定芯片的构架，比如riscv64。")]
    arch: Arch,

    #[clap(long, help = "指定调试连接的端口。")]
    port: u16,
}

impl GdbArgs {
    pub fn gdb(&self) {
        match self.arch {
            Arch::Riscv64 => {
                Ext::new("riscv64-unknown-elf-gdb")
                    .args(["-ex", &format!("target remote localhost:{}", self.port)])
                    .invoke();
            }
        }
    }
}

#[derive(Debug, Args)]
pub(crate) struct QemuArgs {
    #[clap(short, long, help = "指定芯片构架，比如riscv64。")]
    arch: Arch,
    #[clap(long, default_value = "false", help = "是否debug模式编译。")]
    debug: bool,

    #[clap(
        long,
        default_value = "false",
        help = "指定sbi二进制文件作为bios，注意在书中默认采用的是Ubuntu 20.04内置的QEMU，其版本为4.2.1。\n\
    高版本的QEMU需要在SBI中初始化PMP才能运行."
    )]
    bios: bool,

    #[clap(long, help = "指定多核处理器的核数。")]
    smp: Option<u8>,
    // port for gdb to connect. If set, qemu will block and wait gdb to connect.
    #[clap(long, help = "gdb连接的端口，如果设置qemu将会阻塞等带gdb的连接。")]
    gdb: Option<u16>,
}

impl QemuArgs {
    pub fn qemu(self) {
        let arch = self.arch;
        let arch_str = arch.name();
        let bin_benos = BuildConfig::from_args(BuildArgs {
            machine: format!("virt-{}", arch_str),
            debug: self.debug,
        })
        .bin("benos", None);

        let bin_sbi = BuildConfig::from_args(BuildArgs {
            machine: format!("virt-{}", arch_str),
            debug: self.debug,
        })
        .bin("sbi", None);

        let mut qemu = Qemu::system(arch_str);
        qemu.args(["-m", "128M"])
            .arg("-kernel")
            .arg(&bin_benos)
            .args(["-display", "none"])
            .arg("-no-reboot")
            .arg("-nographic")
            .optional(&self.smp, |qemu, smp| {
                qemu.args(["-smp", &smp.to_string()]);
            });
        match arch {
            Arch::Riscv64 => {
                qemu.args(["-machine", "virt"]);
                if self.bios {
                    qemu.arg("-bios").arg(&bin_sbi);
                }
                qemu.args(["-serial", "mon:stdio"]);
            }
        }
        qemu.args([
            "-device",
            &format!(
                "loader,file={},addr=0x80200000",
                bin_benos.to_string_lossy()
            ),
        ])
        .optional(&self.gdb, |qemu, port| {
            qemu.args(["-S", "-dgb", &format!("tcp:{port}")]);
        })
        .invoke();
    }
}

pub(crate) struct BuildConfig {
    arch: Arch,
    debug: bool,
    // 环境变量
    env: HashMap<OsString, OsString>,
    // feature的设置
    features: HashSet<String>,
}

impl BuildConfig {
    pub fn from_args(args: BuildArgs) -> Self {
        // 从根目录的config/machine_features.toml读取配置到MachineConfig中去
        let machine = MachineConfig::select(args.machine).expect("unknown target machine");
        let features = HashSet::from_iter(machine.features.iter().cloned());
        let env = HashMap::new(); // 目前没有使用
        println!("machine_configure: {:?}", machine.manufacture);
        // 获取机器的arch
        let arch = Arch::from_str(&machine.arch)
            .unwrap_or_else(|_| panic!("unknown arch {}", machine.arch));

        Self {
            arch,
            debug: args.debug,
            env,
            features,
        }
    }

    /// 调用cargo命令
    pub fn invoke(&self, src: &str, cargo: impl FnOnce() -> Cargo) {
        println!("features: {:?}", self.features);
        let target_path = PROJECT_DIR
            .join(src)
            .join(format!("{}_target", self.arch.name()));

        let target = std::fs::read_to_string(target_path).unwrap();
        let mut cargo = cargo();
        cargo
            .package(src)
            .features(false, &self.features)
            .target(target)
            .args(["-Z", "build-std=core,alloc"])
            .args(["-Z", "build-std-features=compiler-builtins-mem"])
            .args(["-Z", "unstable-options"])
            .conditional(!self.debug, |cargo| {
                cargo.release();
            });
        println!("env: {:?}", self.env);
        for (key, val) in &self.env {
            println!("set build env: {key:?}: {val:?}");
            cargo.env(key, val);
        }
        cargo.invoke();
    }

    /// 生成纯二进制
    pub fn bin(&self, src: &str, output: Option<PathBuf>) -> PathBuf {
        // 先编译 -C PATH更改编译时的工作目录
        self.invoke(src, || {
            let mut cargo = Cargo::new_naked();
            cargo.args(["-C", src]);
            cargo.arg("build");
            cargo
        });

        let obj = self.target_file_path(src);
        let out = output.unwrap_or_else(|| obj.with_extension("bin"));
        println!("strip {src} to {}", out.display());
        dir::create_parent(&out).unwrap();
        BinUtil::objcopy()
            .arg("--binary-architecture=riscv64")
            .arg(obj)
            .args(["--strip-all", "-O", "binary"])
            .arg(&out)
            .invoke();
        let copy = PROJECT_DIR.join("sbi").join(out.file_name().unwrap());
        if src == "benos" {
            std::fs::copy(&out, copy).unwrap();
        }
        out
    }

    /// 生成target的路径
    #[inline]
    fn target_file_path(&self, src: &str) -> PathBuf {
        PROJECT_DIR
            .join("target")
            .join(format!("{}gc-unknown-none-elf", self.arch.name()))
            .join(if self.debug { "debug" } else { "release" })
            .join(src)
    }
}
